-- update.lua
--[[
Update.lua download:
wget -f https://gitee.com/alanduan123/ocluafiles/raw/master/Utils/Update.lua
wget -f http://192.168.18.138:3000/root/ocluafiles/raw/branch/master/Utils/Update.lua
当前进展：发现无法自动创建目录, 当我在home下创建dhcp目录后,gitee的api请求403,原因是rate limit
]]

local event = require("event")
local term = require("term")
local shell = require("shell")
local computer = require("computer")
local internet = require("internet")
local component = require("component")
local filesystem = require("filesystem")

-- ===============================
-- JSON解析模块
-- ===============================
local JSONParser = {}

function JSONParser.parse(jsonStr)
    local i = 1
    local len = #jsonStr

    local function skip()
        while i <= len do
            local c = jsonStr:sub(i,i)
            if c ~= " " and c ~= "\n" and c ~= "\r" and c ~= "\t" then break end
            i = i + 1
        end
    end

    local function parseValue()
        skip()
        if i > len then return nil end

        local c = jsonStr:sub(i,i)

        if c == '"' then -- 字符串
            i = i + 1
            local start = i
            while i <= len and jsonStr:sub(i,i) ~= '"' do i = i + 1 end
            local result = jsonStr:sub(start, i-1)
            i = i + 1
            return result
        elseif c == '[' then -- 数组
            i = i + 1
            local arr = {}
            local idx = 1
            skip()
            if i <= len and jsonStr:sub(i,i) == ']' then i = i + 1; return arr end

            repeat
                arr[idx] = parseValue()
                idx = idx + 1
                skip()
                if i <= len and jsonStr:sub(i,i) == ',' then i = i + 1 end
                skip()
            until i > len or jsonStr:sub(i,i) == ']'
            if i <= len then i = i + 1 end
            return arr
        elseif c == '{' then -- 对象
            i = i + 1
            local obj = {}
            skip()
            if i <= len and jsonStr:sub(i,i) == '}' then i = i + 1; return obj end

            repeat
                skip()
                local key = parseValue()
                skip()
                if i <= len and jsonStr:sub(i,i) == ':' then i = i + 1 end
                obj[key] = parseValue()
                skip()
                if i <= len and jsonStr:sub(i,i) == ',' then i = i + 1 end
                skip()
            until i > len or jsonStr:sub(i,i) == '}'
            if i <= len then i = i + 1 end
            return obj
        elseif c == 't' then
            i = i + 4
            return true
        elseif c == 'f' then
            i = i + 5
            return false
        elseif c == 'n' then
            i = i + 4
            return nil
        else -- 数字
            local start = i
            if c == '-' then i = i + 1 end
            while i <= len do
                c = jsonStr:sub(i,i)
                if not c:match("[0-9.]") then break end
                i = i + 1
            end
            return tonumber(jsonStr:sub(start, i-1))
        end
    end

    return parseValue()
end

-- ===============================
-- 网络请求模块
-- ===============================
local NetworkManager = {}

function NetworkManager.checkInternet()
    if not component.isAvailable("internet") then
        print("错误: 未检测到互联网组件")
        print("请安装网卡或Ethernet卡后重试")
        print("按任意键退出...")
        event.pull("key")
        return false
    end
    return true
end

function NetworkManager.fetchDirectoryContents(apiPath)
    --local apiUrl = "https://gitee.com/api/v5/repos/alanduan123/ocluafiles/contents/" .. (apiPath or "Utils")  暂时不用gitee，此行不要删除
    local apiUrl = "http://192.168.18.138:3000/api/v1/repos/root/ocluafiles/contents/" .. (apiPath or "Utils")

    print("获取目录内容: " .. (apiPath or "Utils"))

    local requestIterator = internet.request(apiUrl)
    if type(requestIterator) ~= "table" then
        print("API 请求失败")
        os.sleep(2)
        return nil
    end

    -- 收集响应数据
    local jsonChunks = {}
    for chunk in requestIterator do
        table.insert(jsonChunks, chunk)
        if #jsonChunks > 100 then
            print("响应过大，终止处理")
            break
        end
    end

    if #jsonChunks == 0 then
        print("API 响应为空")
        os.sleep(2)
        return nil
    end

    local jsonResponse = table.concat(jsonChunks)

    -- 解析 JSON
    local ok, apiData = pcall(JSONParser.parse, jsonResponse)
    if not ok or type(apiData) ~= "table" or (apiData.message and apiData.message ~= nil) then
        print("API 响应解析失败: " .. (apiData and apiData.message or "未知错误"))
        os.sleep(2)
        return nil
    end

    return apiData
end

function NetworkManager.fetchFileList(path)
    local contents = NetworkManager.fetchDirectoryContents(path)
    if not contents then
        return nil
    end

    local items = {}
    for _, item in ipairs(contents) do
        if item.type == "file" then
            -- 只显示 .lua 文件
            if item.name:sub(-4) == ".lua" then
                table.insert(items, {
                    name = item.name,
                    type = "file",
                    url = item.download_url,
                    path = item.path
                })
            end
        elseif item.type == "dir" then
            table.insert(items, {
                name = item.name,
                type = "directory",
                path = item.path
            })
        end
    end

    print("找到 " .. #items .. " 个项目")
    return items
end

-- 递归获取目录下所有 .lua 文件
function NetworkManager.fetchAllFilesInDirectory(dirPath, baseLocalPath)
    baseLocalPath = baseLocalPath or ""
    local allFiles = {}

    local function collectFiles(path, localPath)
        print("扫描目录: " .. path)
        local contents = NetworkManager.fetchDirectoryContents(path)
        if not contents then
            return
        end

        for _, item in ipairs(contents) do
            if item.type == "file" and item.name:sub(-4) == ".lua" then
                table.insert(allFiles, {
                    name = item.name,
                    url = item.download_url,
                    localPath = filesystem.concat(localPath, item.name)
                })
            elseif item.type == "dir" then
                local subLocalPath = filesystem.concat(localPath, item.name)
                collectFiles(item.path, subLocalPath)
            end
        end
    end

    collectFiles(dirPath, baseLocalPath)
    return allFiles
end

-- 修复：确保目录创建
function NetworkManager.ensureDirectory(filepath)
    local path = filesystem.path(filepath)
    if path and path ~= "" and path ~= "/" and not filesystem.exists(path) then
        print("创建目录: " .. path)
        return filesystem.makeDirectory(path)
    end
    return true
end

function NetworkManager.downloadFile(url, filename)
    print("正在下载 " .. filename .. "...")

    -- 确保目录存在
    if not NetworkManager.ensureDirectory(filename) then
        print("无法创建目录: " .. filesystem.path(filename))
        return false
    end

    local cmd = string.format('wget -f "%s" "%s"', url, filename)
    local result, reason = shell.execute(cmd)

    if result then
        print("下载成功: " .. filename)
        return true
    else
        print("下载失败(" .. filename .. "): " .. tostring(reason))
        return false
    end
end

-- ===============================
-- 文件管理模块
-- ===============================
local FileManager = {}

function FileManager.getBackupList()
    return {
        -- 暂不补充备份列表
          }
end

-- ===============================
-- 导航管理模块
-- ===============================
local NavigationManager = {}

NavigationManager.currentPath = "Utils"
NavigationManager.pathHistory = {}

function NavigationManager.pushPath(path)
    table.insert(NavigationManager.pathHistory, NavigationManager.currentPath)
    NavigationManager.currentPath = path
end

function NavigationManager.popPath()
    if #NavigationManager.pathHistory > 0 then
        NavigationManager.currentPath = table.remove(NavigationManager.pathHistory)
        return true
    end
    return false
end

function NavigationManager.getDisplayPath()
    return NavigationManager.currentPath:gsub("Utils/?", "/")
end

-- 获取相对路径（去除Utils前缀）
function NavigationManager.getRelativePath(fullPath)
    local relativePath = fullPath
    if fullPath:sub(1, 6) == "Utils/" then
        relativePath = fullPath:sub(7) -- 去掉 "Utils/"
    elseif fullPath == "Utils" then
        relativePath = ""
    end
    return relativePath
end

-- ===============================
-- 用户界面模块
-- ===============================
local UI = {}

function UI.showMenu(itemList)
    term.clear()
    print("===== 文件更新工具 =====")
    print("当前路径: " .. NavigationManager.getDisplayPath())
    print("请选择要下载/更新的项目：")

    -- 显示返回上级选项
    if #NavigationManager.pathHistory > 0 then
        print("U. 返回上级目录")
    end

    -- 显示文件和目录
    for i, item in ipairs(itemList) do
        local icon = ""
        local suffix = ""
        if item.type == "file" then
            icon = "📄"
        elseif item.type == "directory" then
            icon = "📁"
            suffix = " (按住S递归下载)"
        end
        print(string.format("%d. %s %s%s", i, icon, item.name, suffix))
    end

    print("\n0. 退出")
    print("A. 全部更新 (仅当前目录)")
    print("D. 递归下载整个目录")
    print("R. 刷新列表")
    print("=======================")

    local optionsText = "请输入选择(0-"..#itemList
    if #NavigationManager.pathHistory > 0 then
        optionsText = optionsText .. ", U"
    end
    optionsText = optionsText .. ", A, D, R, S+数字): "

    term.write(optionsText)
end

function UI.getUserInput()
    local input = term.read()
    if input then
        return input:gsub("[\r\n]", "")
    end
    return ""
end

function UI.confirmAction(message)
    print(message .. " (y/n)")
    local confirm = UI.getUserInput():lower()
    return confirm == "y" or confirm == "yes"
end

function UI.waitForKey(message)
    if message then
        print(message)
    end
    event.pull("key")
end

-- ===============================
-- 主程序控制器
-- ===============================
local UpdateManager = {}

function UpdateManager.downloadAllFiles(itemList)
    local files = {}
    for _, item in ipairs(itemList) do
        if item.type == "file" then
            table.insert(files, item)
        end
    end

    if #files == 0 then
        print("当前目录没有文件可下载")
        UI.waitForKey("按任意键继续...")
        return
    end

    print("开始更新当前目录的所有文件...")
    local successCount = 0

    for i, file in ipairs(files) do
        print(string.format("[%d/%d] %s", i, #files, file.name))
        if NetworkManager.downloadFile(file.url, file.name) then
            successCount = successCount + 1
        end
    end

    print(string.format("更新完成！成功：%d/%d", successCount, #files))
    UI.waitForKey("按任意键继续...")
end

-- 修复：递归下载整个当前目录
function UpdateManager.downloadDirectoryRecursively()
    local currentPath = NavigationManager.currentPath
    local displayPath = NavigationManager.getDisplayPath()
    print("准备递归下载目录: " .. displayPath)

    if not UI.confirmAction("这将下载该目录及所有子目录中的 .lua 文件，确定要继续吗?") then
        print("取消操作")
        os.sleep(1)
        return
    end

    -- 修复问题2：使用相对路径作为本地基础路径
    local relativePath = NavigationManager.getRelativePath(currentPath)
    local baseLocalPath = relativePath

    local allFiles = NetworkManager.fetchAllFilesInDirectory(currentPath, baseLocalPath)

    if not allFiles or #allFiles == 0 then
        print("未找到任何 .lua 文件")
        UI.waitForKey("按任意键继续...")
        return
    end

    print(string.format("找到 %d 个 .lua 文件，开始下载...", #allFiles))
    local successCount = 0

    for i, file in ipairs(allFiles) do
        print(string.format("[%d/%d] %s", i, #allFiles, file.localPath))
        if NetworkManager.downloadFile(file.url, file.localPath) then
            successCount = successCount + 1
        end
        os.sleep(0.1) -- 避免请求过快
    end

    print(string.format("递归下载完成！成功：%d/%d", successCount, #allFiles))
    UI.waitForKey("按任意键继续...")
end

-- 修复问题1：递归下载指定目录
function UpdateManager.downloadSpecificDirectoryRecursively(item)
    local targetPath = item.path
    local dirName = item.name

    print("准备递归下载目录: " .. dirName)

    if not UI.confirmAction("确定要递归下载 " .. dirName .. " 目录及其所有子目录中的 .lua 文件吗?") then
        print("取消操作")
        os.sleep(1)
        return
    end

    -- 使用目录名作为本地基础路径
    local allFiles = NetworkManager.fetchAllFilesInDirectory(targetPath, dirName)

    if not allFiles or #allFiles == 0 then
        print("未找到任何 .lua 文件")
        UI.waitForKey("按任意键继续...")
        return
    end

    print(string.format("找到 %d 个 .lua 文件，开始下载...", #allFiles))
    local successCount = 0

    for i, file in ipairs(allFiles) do
        print(string.format("[%d/%d] %s", i, #allFiles, file.localPath))
        if NetworkManager.downloadFile(file.url, file.localPath) then
            successCount = successCount + 1
        end
        os.sleep(0.1) -- 避免请求过快
    end

    print(string.format("递归下载完成！成功：%d/%d", successCount, #allFiles))
    UI.waitForKey("按任意键继续...")
end

function UpdateManager.downloadSingleFile(item)
    print(string.format("你选择了: %s %s",
            item.type == "file" and "📄" or "📁", item.name))

    if item.type == "file" then
        if UI.confirmAction("确定要下载/更新此文件吗?") then
            if NetworkManager.downloadFile(item.url, item.name) then
                print("更新成功！按 R 键重启，其他键继续...")
                local _, _, key = event.pull("key")
                if key == 114 or key == 82 then -- r 或 R
                    computer.shutdown(true)
                end
            else
                UI.waitForKey("更新失败，按任意键继续...")
            end
        else
            print("取消操作")
            os.sleep(1)
        end
    elseif item.type == "directory" then
        -- 进入目录
        NavigationManager.pushPath(item.path)
        return "navigate"
    end
end

function UpdateManager.refreshFileList()
    local itemList = NetworkManager.fetchFileList(NavigationManager.currentPath)
    if itemList and #itemList >= 0 then
        return itemList
    else
        print("API获取失败")
        if NavigationManager.currentPath == "Utils" then
            print("使用备份文件列表")
            return FileManager.getBackupList()
        else
            print("返回上级目录")
            if NavigationManager.popPath() then
                return NetworkManager.fetchFileList(NavigationManager.currentPath)
            end
            return {}
        end
    end
end

function UpdateManager.handleUserChoice(input, itemList)
    term.clear()

    if input == "0" then
        term.clear()
        print("退出更新工具")
        return nil, false -- 返回 nil 表示退出

    elseif input:lower() == "u" and #NavigationManager.pathHistory > 0 then
        NavigationManager.popPath()
        return UpdateManager.refreshFileList(), true

    elseif input:lower() == "r" then
        print("刷新文件列表...")
        os.sleep(1)
        return UpdateManager.refreshFileList(), true

    elseif input:lower() == "a" then
        UpdateManager.downloadAllFiles(itemList)
        return itemList, true

    elseif input:lower() == "d" then
        UpdateManager.downloadDirectoryRecursively()
        return itemList, true

        -- 修复问题1：支持 S+数字 格式递归下载指定目录
    elseif input:sub(1,1):lower() == "s" then
        local choice = tonumber(input:sub(2))
        if choice and choice > 0 and choice <= #itemList then
            local item = itemList[choice]
            if item.type == "directory" then
                UpdateManager.downloadSpecificDirectoryRecursively(item)
            else
                print("只能对目录使用递归下载功能")
                os.sleep(2)
            end
        else
            print("无效的目录序号")
            os.sleep(2)
        end
        return itemList, true

    else
        local choice = tonumber(input)
        if choice and choice > 0 and choice <= #itemList then
            local result = UpdateManager.downloadSingleFile(itemList[choice])
            if result == "navigate" then
                return UpdateManager.refreshFileList(), true
            end
        else
            print("无效选择，请输入正确的序号或命令")
            os.sleep(2)
        end
        return itemList, true
    end
end

function UpdateManager.run()
    if not NetworkManager.checkInternet() then
        return
    end

    print("网络组件可用，启动更新程序...")
    os.sleep(1)

    local itemList = UpdateManager.refreshFileList()
    local running = true

    while running do
        UI.showMenu(itemList)
        local input = UI.getUserInput()
        itemList, running = UpdateManager.handleUserChoice(input, itemList)
    end
end

-- ===============================
-- 程序入口
-- ===============================
UpdateManager.run()
